// This generator will use the old style
// with default methods
import { ON_RESULT_PROP_NAME, ON_ERROR_PROP_NAME } from 'jsonql-constants'

import methodsGenerator from './methods-generator'
import { createEvt } from '../utils'


/**
 * Group all the same methods together
 * @param {object} ee event emitter
 * @param {string} type query, mutation or auth
 * @param {string} resolverName use as the guide
 * @param {array} args from the call
 * @return {function} the handler itself
 */
const handler = (ee, type) => {
  // we don't run validate here because we want until the contract is ready
  return (resolverName, ...args) => (
    new Promise((resolver, rejecter) => {
      // this are the callbacks
      ee.$only(createEvt(type, resolverName, ON_RESULT_PROP_NAME), resolver)
      ee.$only(createEvt(type, resolverName, ON_ERROR_PROP_NAME), rejecter)
      // this is the main call
      ee.$trigger(type, { resolverName, args })
    })
  )
}

/**
 * @param {object} ee eventEmitter
 * @param {object} contract the map
 * @param {object} config configuration
 */
const validateRegisteredEvents = (ee, contract, config) => {
  const storedEvt = ee.$queues;
  const debug = config.debugOn;
  if (debug) {
    console.info('(validateRegisteredEvents)', 'storedEvt', storedEvt)
  }
  storedEvt.forEach(args => {
    let [type, payload] = args;
    let { resolverName } = payload;
    if (debug) {
      console.info('(validateRegisteredEvents)', type, resolverName)
    }
    if (!contract[type][resolverName]) {
      throw new Error(`${type}.${resolverName} not existed in contract!`)
    }
  })
}

/**
 * set up all the event handlers once the contract is ready
 * @param {object} jsonqlInstance what the name said
 * @param {object} ee event emitter
 * @param {object} config the configuration
 * @param {object} contract the map
 * @return {void} nothing
 */
function setupEventHandlers(jsonqlInstance, ee, config, contract) {
  let methods = methodsGenerator(jsonqlInstance, ee, config, contract)
  validateRegisteredEvents(ee, contract, config)
  // create handler
  for (let type in methods) {
    // setup event listeners - only one listener per type
    ee.$only(type, function({resolverName, args}) {
      if (methods[type][resolverName]) {
        Reflect.apply(methods[type][resolverName], null, args)
          .then(result => {
            ee.$trigger(createEvt(type, resolverName, ON_RESULT_PROP_NAME), result)
          })
          .catch(err => {
            ee.$trigger(createEvt(type, resolverName, ON_ERROR_PROP_NAME), err)
          })
      } else {
        console.error(`${resolverName} is not defined in the contract!`)
      }
    })
  }
  // all done now release the queue if any
  setTimeout(() => {
    ee.$suspend = false;
  }, 1)
}

/**
 * @param {object} jsonqlInstance jsonql class instance
 * @param {object} config options
 * @param {object} contractPromise an unresolve promise
 * @param {object} ee eventEmitter
 * @return {object} constructed functions call
 */
const generator = (jsonqlInstance, config, contractPromise, ee) => {
  ee.$suspend = true; // hold all the calls
  // wait for the promise to resolve
  contractPromise.then(contract => {
    setupEventHandlers(jsonqlInstance, ee, config, contract)
  })
  // construct the api
  let obj = {
    query: handler(ee, 'query'),
    mutation: handler(ee, 'mutation'),
    auth: handler(ee, 'auth')
  }
  // allow getting the token for valdiate agains the socket
  obj.getToken = () => jsonqlInstance.rawAuthToken;
  // this will pass to the ws-client if needed
  // obj.eventEmitter = ee;
  // this will require a param
  if (config.exposeContract) {
    obj.getContract = () => jsonqlInstance.get()
  }
  if (config.enableAuth) {
    obj.userdata = () => jsonqlInstance.userdata;
  }
  obj.version = '__VERSION__';
  // output
  return obj;
}

export default generator;
